/**
 * http request client.
 */
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import qs from "qs";
import * as checkType from './checkType';
import deepmerge from "deepmerge";

interface StrToAnyObj {
  [index: string]: any
}

export type Resp<T> = { code: string | number, data: T, msg: string }
export type PromiseResp<T> = Promise<Resp<T>>
export interface RequestOptions extends AxiosRequestConfig {
  isFormatOriginRespData?: boolean;   // 是否直接包装原始的响应数据
  isDownLoad?: boolean;
  isShowErrorPrompt?: boolean;
  isUrlParamsSerializer?: boolean;     // 是否开启url参数序列化
  formatDownloadFilename?: (filename: string) => string;
}

// TODO 解决IE报warning Unhandled Rejections Error 参数书不正确的问题
// @ts-ignore
Promise._unhandledRejectionFn = function (rejectError) { };

const tokenStoreKey = "token";  // token的存储token key

let requestConf: {
  apiDomain: string,
  apiResponseCode: {
    apiSuccessCode?: string,
    errorCode?: string,
    noLoginCode?: string,
    noPermissionCode?: string,
    invalidParamCode?: string
  },
  onErrorMsg: (msg: string) => void,
  redirectLogin: () => void,
  onAfterResp: (resp: Resp<any>) => void
} = {
  apiDomain: "",
  apiResponseCode: {
    apiSuccessCode: "200",
    errorCode: "error",
    noLoginCode: "401",
    noPermissionCode: "403",
    invalidParamCode: "400"
  },
  onErrorMsg: (_: string) => { },
  redirectLogin: () => { },
  onAfterResp: (_: Resp<any>) => { }
}

/**
 * 设置请求配置
 */
export const setRequestConf = (config: Partial<typeof requestConf>) => {
  requestConf = deepmerge(requestConf, config);
}

export const getRequestConf = () => requestConf;

/**
 * 保存token
 */
export const setToken = (token: string) => {
  window.localStorage.setItem(tokenStoreKey, token)
}

/**
 * 获取token
 */
export const getToken = () => {
  return window.localStorage.getItem(tokenStoreKey)
}

/**
 * 删除token
 */
export const removeToken = () => {
  return window.localStorage.removeItem(tokenStoreKey)
}

const Singleton = (function () {
  let instantiated: AxiosInstance;

  function init() {
    return axios.create({
      // baseURL: apiDomain,

      // `withCredentials`指示是否跨站点访问控制请求
      withCredentials: true,

      // “responseType”表示服务器将响应的数据类型
      // 包括 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
      responseType: 'json',

      // headers`是要发送的自定义 headers
      headers: {
        // 'X-Requested-With': 'XMLHttpRequest'
      },

    });
  }

  return {
    getInstance: function () {
      if (!instantiated) {
        instantiated = init();
      }
      return instantiated;
    }
  };
})();

/**
 * 格式化请求options
 */
const formatOptions = (options: RequestOptions) => {
  // 是否开启url参数序列化
  if (options.isUrlParamsSerializer) {
    options.paramsSerializer = (params) => {
      return qs.stringify(params)
    }
  }

  return options;
}

/**
 * 组装request
 */
const _request = <T>(options: RequestOptions = {}): PromiseResp<T> => {
  return new Promise((resolve, reject) => {
    const { redirectLogin, apiResponseCode, onAfterResp, onErrorMsg } = requestConf;
    let { apiSuccessCode, errorCode, noLoginCode, invalidParamCode } = apiResponseCode;

    const rejectError = (data: any, showErrorPrompt = true) => {
      if (options.isShowErrorPrompt && showErrorPrompt && data.msg) {
        // prompt.error(data.msg, 4);
        onErrorMsg?.(data.msg)
      }
      reject(data);
    }

    // 设置请求header中的认证token
    const token = getToken();
    if (token) {
      options.headers = {
        ...options.headers,
        Authorization: `Bearer ${token}`
      }
    }
    // options.paramsSerializer
    Singleton.getInstance().request(formatOptions(options)).then((resp: AxiosResponse) => {
      if (options.isDownLoad) {
        processDownload(resp, options);
        resolve({ code: apiSuccessCode, data: resp.data, msg: "download success" });
      }

      if (resp.status === 200) {
        if (options.isFormatOriginRespData) {
          return resolve({ code: apiSuccessCode, data: resp.data, msg: "请求成功" });
        }

        if (checkType.isPlainObject(resp.data)) {
          let { data, code, message, msg } = resp.data;
          // 朴素对象中不包含code时，不做处理
          if (code === undefined) {
            onAfterResp && onAfterResp({ code: apiSuccessCode, data: resp.data, msg: "请求成功" });
            resolve({ code: apiSuccessCode, data: resp.data, msg: "请求成功" });
            return;
          }

          msg = msg || message;
          const respInfo = { code, data, msg };
          onAfterResp && onAfterResp(respInfo);

          if (apiSuccessCode == code || (Array.isArray(apiSuccessCode) && apiSuccessCode.map((code) => code.toString()).includes(code.toString()))) {
            return resolve({ code, data, msg });
          } else if (code == noLoginCode) {  // 未登录
            removeToken();
            redirectLogin && redirectLogin();
            return rejectError({ code, data, msg }, false);
          } else if (code == invalidParamCode) { // 参数校验失败
            return rejectError({ code, data, msg });
          } else { // 系统内部错误
            return rejectError({ code, data, msg });
          }
        }
        onAfterResp && onAfterResp({ code: apiSuccessCode, data: resp.data, msg: "请求成功" });
        resolve({ code: apiSuccessCode, data: resp.data, msg: "请求成功" });
      } else {
        const { data, code, msg } = resp.data;
        // @ts-ignore
        const respInfo = { code, data, msg: msg || resp.message }

        onAfterResp && onAfterResp(respInfo);
        rejectError(respInfo);
      }
    }).catch((error: any) => {
      let errorMsg = error.message;
      if (error.response && checkType.isPlainObject(error.response.data)) {
        let { message, msg } = error.response.data;
        errorMsg = msg || message;
      }

      const respInfo = {
        code: errorCode,
        // @ts-ignore
        data: null,
        msg: errorMsg
      }

      onAfterResp && onAfterResp(respInfo);
      if (error.response && error.response.status == noLoginCode) {  // 未登录
        redirectLogin && redirectLogin();
        return rejectError(respInfo, false);
      }

      rejectError(respInfo);
    });
  });
};

/**
 * 处理下载
 */
const processDownload = (resp: AxiosResponse, options: RequestOptions) => {
  let filename = Date.now().toString();
  if (resp.headers['content-disposition']) {
    const matchFilename = resp.headers['content-disposition']?.match(/filename=(.*)/);
    if (matchFilename) filename = decodeURI(matchFilename[1]);
  }
  if (options.formatDownloadFilename) filename = options.formatDownloadFilename(filename);
  // const blob = new Blob([resp.data], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'}); // application/vnd.openxmlformats-officedocument.spreadsheetml.sheet这里表示xlsx类型
  // const blob = new Blob([resp.data], {type: 'application/zip;charset=utf-8'}); // application/zip
  const blob = new Blob([resp.data], { type: resp.headers['content-type'] });
  let downloadElement = document.createElement('a');
  const href = window.URL.createObjectURL(blob);      //创建下载的链接
  downloadElement.href = href;
  downloadElement.download = filename.replace(/^"/, "").replace(/"$/, "");                //下载后文件名
  document.body.appendChild(downloadElement);
  downloadElement.click();                            //点击下载
  document.body.removeChild(downloadElement);         //下载完成移除元素
  window.URL.revokeObjectURL(href);                   //释放掉blob对象
};

/**
 * get请求
 */
export function get<T = any>(url: string, params: StrToAnyObj = {}, options?: RequestOptions) {
  options = Object.assign({
    isShowErrorPrompt: true,
    url,
    method: 'get',
    params: params,
  }, options || {});

  return _request<T>(options);
}

/**
 * post请求
 */
export function post<T = any>(url: string, params: StrToAnyObj = {}, options?: RequestOptions) {
  let requestParams = new URLSearchParams();
  for (let [k, v] of Object.entries(params)) {
    requestParams.append(k, v);
  }

  options = Object.assign({
    isShowErrorPrompt: true,
    url,
    method: 'post',
    data: requestParams,
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    }
  }, options || {});

  return _request<T>(options);
}

/**
 * post json请求
 */
export function postJSON<T = any>(url: string, params = {}, options?: RequestOptions) {
  options = Object.assign({
    isShowErrorPrompt: true,
    url,
    method: 'post',
    data: params,
    headers: {
      'Content-Type': 'application/json'
    }
  }, options || {});

  return _request<T>(options);
}

/**
 * 请求上传文件
 */
export function upload<T = any>(url: string, params: StrToAnyObj = {}, onUploadProgress?: (progressEvent: ProgressEvent) => void, options?: RequestOptions) {
  if (!(params instanceof FormData)) {
    let formData = new FormData();
    for (let [k, v] of Object.entries(params)) {
      if (Array.isArray(v)) {
        // v.forEach((item) => formData.append(`${k}[]`, item));
        v.forEach((item) => formData.append(`${k}`, item));
      } else {
        if (v) formData.append(k, v);
      }
    }
    params = formData;
  }

  options = Object.assign({
    isShowErrorPrompt: true,
    url,
    method: 'post',
    data: params,
    // `onUploadProgress`允许处理上传的进度事件
    onUploadProgress: onUploadProgress || ((_: any) => { }),
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  }, options || {});

  return _request<T>(options);
}

/**
 * restful delete
 */
export function del<T = any>(url: string, params = {}, options?: RequestOptions) {
  options = Object.assign({
    isShowErrorPrompt: true,
    url,
    method: 'delete',
    data: params,
    headers: {
      'Content-Type': 'application/json'
    }
  }, options || {});

  return _request<T>(options);
}

/**
 * restful put
 */
export function put<T = any>(url: string, params = {}, options?: RequestOptions) {
  options = Object.assign({
    isShowErrorPrompt: true,
    url,
    method: 'put',
    data: params,
    headers: {
      'Content-Type': 'application/json'
    }
  }, options || {});

  return _request<T>(options);
}


/**
 * download
 */
export const download = <T = any>(url: string, params: StrToAnyObj = {}, options?: RequestOptions) => get<T>(url, params, {
  isDownLoad: true,
  responseType: "arraybuffer",
  ...options
});

/**
 * 并发执行多个请求
 * @returns {Promise.<*>}
 */
export function all<T = any[]>(args: Promise<T>[] = []) {
  // @ts-ignore
  return Array.isArray(args) ? Promise.all(args) : Promise.all([...arguments]);
}

/**
 * 格式化URL参数
 */
export function formatUrlParams(url: string, params: StrToAnyObj = {}) {
  Object.keys(params).forEach((key, index) => {
    if (index === 0 && url.indexOf('?') === -1) {
      url += '?' + key + '=' + params[key];
    } else {
      url += '&' + key + '=' + params[key];
    }
  });

  return url;
}

/**
 * 模拟响应数据
 */
export function mockRespData<T = any>(data: T, isError = false): PromiseResp<T> {
  const { apiSuccessCode, errorCode } = requestConf.apiResponseCode;

  return new Promise((resolve, reject) => {
    if (isError) {
      setTimeout(() => reject({
        code: errorCode,
        msg: "error",
        data
      }), 500);
    } else {
      setTimeout(() => resolve({
        code: apiSuccessCode,
        msg: "success",
        data
      }), 500);
    }
  })
}


/**
 * 生成响应数据
 * @return {Promise<any>}
 */
export function mkRespData<T = any>(data: T, code = "success", msg = "success") {
  return {
    code,
    msg,
    data
  }
}

/**
 * 包装响应数据的方法
 */
export function wrapperRespDataFN<T = any>(callback: () => Promise<T>): PromiseResp<T> {
  const { apiSuccessCode, errorCode } = requestConf.apiResponseCode;

  return callback().then((data) => {
    return {
      code: apiSuccessCode,
      msg: "success",
      data
    }
  }).catch((e) => {
    return {
      code: errorCode,
      msg: e,
      data: null
    }
  })
}
